#include "stdafx.h"

#include "core/image.h"
#include "rt/basic_definitions.h"
#include "rt/geometry_group.h"

#include "impl/lwobject.h"
#include "impl/phong_shaders.h"
#include "impl/basic_primitives.h"
#include "impl/perspective_camera.h"
#include "impl/integrator.h"
#include "rt/renderer.h"
#include "impl/samplers.h"
#include "impl/proceduralShader.h"

#include <time.h>



class ReflectiveandRefractivePhongShader : public DefaultPhongShader
{
protected:
	float2 m_texCoord; 
	Point m_position;
public:
	float reflCoef;
	float density;

	virtual float getDensity(){return density;}

	virtual void setPosition(const Point& _point) { m_position = _point; }

	virtual void setNormal(const Vector& _normal) 
	{
		DefaultPhongShader::setNormal(_normal);
	}

	virtual Vector getNormal() const 
	{ 
		return m_normal;
	}

	virtual float4 getIndirectRadiance(const Vector &_out, Integrator *_integrator) const
	{ 
		//the density of the volume the ray is comming from
		float raydens = ((IntegratorImpl*)_integrator)->incomRay.density;

		float dens = density;
		if(density==raydens)
			dens = 1.0003f;

		float critangle;
		if(raydens>dens){
			critangle=asin(raydens/dens);
			
		}
		else
			critangle=FLT_MAX;

		Ray rfl;
		rfl.o = m_position;

		float pi = 3.14159265358979323846f;
		//for the refl vector
		Vector n = getNormal();
		if(n*_out/(n.len()*_out.len()) < 0.f)
			n = -getNormal();
		Vector v = n * fabs(n * _out);
		rfl.d = _out + 2 * (v - _out);

		//for the refr vector
		Vector l = ~(-_out);
		Ray rfr;
		rfr.o = m_position;
		bool totalrefl =false;
		//snell's law
		float teta1 = n*(-l);
		//std::cout<<"sqrt(bla) "<<1-(raydens/density)*(raydens/density)*(1-teta1*teta1)<<std::endl;
		float sqareroot = 1-(raydens/dens)*(raydens/dens)*(1-teta1*teta1);
		float teta2;
		if(sqareroot<0)
		{
			//totalrefl=true;
			//sqareroot=0;
			//std::cout<<acos(teta1)<<std::endl;

			teta2=teta1*dens/raydens;
			//std::cout<<acos(teta1)*180/pi<<"-"<<acos(teta2)*180/pi<<std::endl;
			//std::cout<<raydens<<"-"<<dens<<std::endl;
		}
		else
			teta2 = sqrt(sqareroot);
		if(acos(teta2)>critangle)
		{
			totalrefl=true;
		}
		if(n*(-l)>0)
			rfr.d = (raydens/dens)*l + ((raydens/dens)*teta1 - teta2)*n;
		else
			rfr.d = (raydens/dens)*l + ((-raydens/dens)*teta1 + teta2)*n;
		
		//std::cout<<"teta1 "<<teta1<<" teta2 "<<teta2<<std::endl; 
		float a = acos(teta1),
			b = acos(teta2);
		if(totalrefl){
			return _integrator->getRadiance(rfl) * float4::rep(reflCoef);
		}
			//Fresnel term
		float r1 = (dens*a-raydens*b)/(dens*a+raydens*b),
			r2 = (raydens*a-dens*b)/(raydens*a+dens*b),
			f = 0.5f*(r1*r1+r2*r2);
		//std::cout<<"F "<<f<<std::endl;
		//f = 1.0f;

		rfl.density=dens;
		rfr.density=dens;
		float4 reflRad = _integrator->getRadiance(rfl),
			refrRad = _integrator->getRadiance(rfr);

			//the radiances multiplied with the percentage F
		float4	refl = reflRad * float4::rep(f),
			refr = refrRad * float4::rep(1.f-f);
		//std::cout<<critangle<<"-"<<f<<"-"<<refl.x<<"+"<<refl.y<<"+"<<refl.z<<"+"<<refl.w<<"-"<<refr.x<<"+"<<refr.y<<"+"<<refr.z<<"+"<<refl.w<<std::endl;
		
		return (refl+refr) * float4::rep(reflCoef);
	}

	virtual void setTextureCoord(const float2& _texCoord) { m_texCoord = _texCoord;}

	_IMPLEMENT_CLONE(ReflectiveandRefractivePhongShader);
};


class ReflectivePhongShader : public DefaultPhongShader
{
protected:
	float2 m_texCoord; 
	Point m_position;
public:
	float reflCoef;

	virtual void setPosition(const Point& _point) { m_position = _point; }

	/*virtual void setNormal(const Vector& _normal) 
	{
		DefaultPhongShader::setNormal(_normal);
	}

	virtual Vector getNormal() const 
	{ 
		return m_normal;
	}*/

	virtual float4 getIndirectRadiance(const Vector &_out, Integrator *_integrator) const
	{ 
		Ray r;
		r.o = m_position;

		Vector n = getNormal();
		if(n*_out/(n.len()*_out.len()) < 0.f)
			n = -getNormal();
		Vector v = n * fabs(n * _out);
		r.d = _out + 2 * (v - _out);

		return _integrator->getRadiance(r) * float4::rep(reflCoef);
	}

	virtual void setTextureCoord(const float2& _texCoord) { m_texCoord = _texCoord;}

	_IMPLEMENT_CLONE(ReflectivePhongShader);
};


void ourscene(int width, int height, bool acceleration)
{
	Image img(width,height);


	img.addRef();

	time_t starttime;
	time_t endtime;
	starttime = time(NULL);
	std::cout << "read start" << std::endl;

	//Set up the scene
	GeometryGroup scene;
	LWObject cow;
	cow.read("models/bath/bath.obj", true);
	//cow.read("models/bump_test.obj", true);
	//cow.read("models/room_glassplane.obj", true);
	//cow.read("models/room_teapot.obj", true);
	//cow.read("models/marble_wood.obj", true);

	endtime = time(NULL);
	std::cout << "read end in "<< (endtime-starttime) << std::endl;
	starttime = time(NULL);
	std::cout << "build start"<< std::endl;


	cow.addReferencesToScene(scene.primitives);
	BVH baum;
	scene.rebuildIndex(acceleration);

	baum = scene.getTree();

	endtime = time(NULL);
	std::cout << "build end in "<< (endtime-starttime) << std::endl;

	ReflectiveandRefractivePhongShader sh4;
	sh4.diffuseCoef = float4(0.2f, 0.2f, 0.2f, 0.2f);
	sh4.ambientCoef = sh4.diffuseCoef;
	sh4.specularCoef = float4::rep(0.8f);
	sh4.specularExponent = 10000.f;
	sh4.reflCoef = 0.8f;
	sh4.density = 1.5f;
	sh4.addRef();

	ProceduralShader sh5;
	sh5.ambientCoef = float4(0.2f, 0.2f, 0.2f, 0.2f);
	sh5.specularCoef = float4::rep(0.8f);
	sh5.specularExponent = 10.f;
	sh5.gapsize=0.04f;
	sh5.addRef();

	/*bumpPhongShader sh5;
	sh5.ambientCoef = float4(0.2f, 0.2f, 0.2f, 0.2f);
	sh5.specularCoef = float4::rep(0.8f);
	sh5.specularExponent = 10.f;
	sh5.diffuseCoef =sh5.ambientCoef;
	sh5.addRef();*/

	ReflectivePhongShader mirror;
	mirror.diffuseCoef = float4(0.2f, 0.2f, 0.2f, 0.2f);
	mirror.ambientCoef = sh4.diffuseCoef;
	mirror.specularCoef = float4::rep(0.8f);
	mirror.specularExponent = 10000.f;
	mirror.reflCoef = 0.8f;
	mirror.addRef();

	//bump.materials[bump.materialMap["Material__25"]].shader= &sh5;


	//cow.materials[cow.materialMap["Floor"]].shader= &sh5;

	//cow.materials[cow.materialMap["floor"]].shader= &sh5;
	
	cow.materials[cow.materialMap["floor"]].shader = &sh5;
	cow.materials[cow.materialMap["mirror_glass"]].shader = &mirror;
	cow.materials[cow.materialMap["glass"]].shader = &sh4;
	cow.materials[cow.materialMap["shower_glass"]].shader = &sh4;
	cow.materials[cow.materialMap["drop"]].shader = &sh4;

	//room with box

	//cow.materials[cow.materialMap["Box"]].shader = &sh4;


	//cow.materials[cow.materialMap["Box"]].shader = &sh4;

	//room_glassplane
	//cow.materials[cow.materialMap["wire_pot"]].shader = &sh4;
	//room with teapot
	//cow.materials[cow.materialMap["Box"]].shader = &sh4;
/*
	ReflectivePhongShader sh5;
	sh5.diffuseCoef = float4(0.2f, 0.2f, 0.2f, 0.2f);
	sh5.ambientCoef = sh4.diffuseCoef;
	sh5.specularCoef = float4::rep(0.8f);
	sh5.specularExponent = 10000.f;
	sh5.reflCoef = 0.4f;
	sh5.addRef();
	cow.materials[cow.materialMap["Ceiling"]].shader = &sh5;*/

 	//Enable bi-linear filtering on the walls
	//((TexturedPhongShader*)cow.materials[cow.materialMap["Stones"]].shader.data())->diffTexture->filterMode = Texture::TFM_Bilinear;
 	//((TexturedPhongShader*)cow.materials[cow.materialMap["Stones"]].shader.data())->amibientTexture->filterMode = Texture::TFM_Bilinear;
	//((TexturedPhongShader*)cow.materials[cow.materialMap["Stones_stones_diffuse.png"]].shader.data())->diffTexture->filterMode = Texture::TFM_Bilinear;
 	//((TexturedPhongShader*)cow.materials[cow.materialMap["Stones_stones_diffuse.png"]].shader.data())->amibientTexture->filterMode = Texture::TFM_Bilinear;
	//((bumpPhongShader*)cow.materials[cow.materialMap["Stones"]].shader.data())->diffTexture->filterMode = Texture::TFM_Bilinear;
	//((bumpPhongShader*)cow.materials[cow.materialMap["Stones"]].shader.data())->amibientTexture->filterMode = Texture::TFM_Bilinear;
	

	//Set up the cameras
	//room_glassplane
	//PerspectiveCamera cam1(Point(2.0f, -4.f, 6.f), Vector(-0.6f, 1.f, 0.f), Vector(0, 0, 1), 30, //Point(-9.398149f, -6.266083f, 5.348377f), Vector(-6.324413f, -2.961229f, 4.203216f)
	//	std::make_pair(img.width(), img.height()));

	//room_glassplane
	//PerspectiveCamera cam1(Point(10.0f, 20.f, 10.f), Point(10.f, 0.f, 2.f), Vector(0, 0, 1), 30, //Point(-9.398149f, -6.266083f, 5.348377f), Vector(-6.324413f, -2.961229f, 4.203216f)
	//	std::make_pair(img.width(), img.height()));

	//room_box
	//PerspectiveCamera cam1(Point(-25.f, 0.f, 6.f), Vector(1.f, 0.25f, 0.f), Vector(0, 0, 1), 30, //Point(-9.398149f, -6.266083f, 5.348377f), Vector(-6.324413f, -2.961229f, 4.203216f)
	//	std::make_pair(img.width(), img.height()));

	//bath
	PerspectiveCamera cam1(Point(195.f, 65.f, 80.f), Vector(-1.f, -0.6f, -0.5f), Vector(0, 0, 1), 30, //Point(100.f, 70.f, 15.f)
		std::make_pair(img.width(), img.height()));
	//bath -> shower
	//PerspectiveCamera cam1(Point(60.f, 60.f, 70.f), Vector(-1.f, -1.f, -0.5f), Vector(0, 0, 1), 30, //Point(100.f, 70.f, 15.f)
	//	std::make_pair(img.width(), img.height()));

	//dof
	//PerspectiveCamera cam1(Point(30.f, 15.f, 10.f), Point(0.f, 2.f, 2.f), Vector(0, 0, 1), 30, //Point(-9.398149f, -6.266083f, 5.348377f), Vector(-6.324413f, -2.961229f, 4.203216f)
	//	std::make_pair(img.width(), img.height()));

	//bump_test
	//PerspectiveCamera cam1(Point(0.f, -300.f, 300.f), Point(0.f, 0.f, 0.f), Vector(0, 0, 1), 45, //Point(-9.398149f, -6.266083f, 5.348377f), Vector(-6.324413f, -2.961229f, 4.203216f)
	//	std::make_pair(img.width(), img.height()));

	//room with teapot
	//PerspectiveCamera cam1(Point(-4.f, -12.f, 5.f), Vector(0.f, 1.f, -0.2f), Vector(0, 0, 1), 30, //Point(-9.398149f, -6.266083f, 5.348377f), Vector(-6.324413f, -2.961229f, 4.203216f)
	//	std::make_pair(img.width(), img.height()));

	//marble_wood
	//PerspectiveCamera cam1(Point(0.0f, -100.0f, 25.0f), Point(0.0, 0.0f, 0.0f), Vector(0, 0, 1), 30,
	//	std::make_pair(img.width(), img.height()));

	//raum.obj
	//PerspectiveCamera cam1(Point(0.f, 0.f, 10.f), Point(0.f, 0.f, 0.f), Vector(0, 0, 1), 30,
	//	std::make_pair(img.width(), img.height()));

	//PerspectiveCamera cam2(Point(2.699700f, 6.437226f, 0.878297f), Point(4.337114f, 8.457443f,- 0.019007f), Vector(0, 0, 1), 30,
	//	std::make_pair(img.width(), img.height()));
	

	//cam1.focalpoint = Point(8.f, 9.f, 3.f);
	//cam1.lenssize = 0.f;

	//cam1.focalpoint = Point(-2.f, -5.f, 3.f);
	//cam1.lenssize = 0.5f;
	
	cam1.focalpoint = Point(122.f, 3.f, 30.f);
	cam1.lenssize = 0.2f;

	cam1.addRef();
	//cam2.addRef();

	//Set up the integrator
	IntegratorImpl integrator;
	integrator.addRef();
	integrator.scene = &scene;

	PointLightSource pls;

	pls.falloff = float4(0, 0, 1, 0);

	//room/cow
	//pls.intensity  = float4::rep(0.9f);
	//pls.position = Point(-2.473637f, 3.119330f, 9.571486f);

	//teapot
	//pls.intensity  = float4::rep(0.9f);
	//pls.position = Point(10.f, 10.f, 15.f);

	//dof
	//pls.intensity  = float4::rep(0.9f);
	//pls.position = Point(20.f, 20.f, 15.f);

	//bump
	//pls.intensity  = float4::rep(0.9f);
	//pls.position = Point(0.f, 300.f, 300.f);

	//marble_wood
	//pls.intensity  = float4::rep(0.7f);
	//pls.position = Point(0.0f, -100.0f, 100.0f);
	
	//raum
	//pls.intensity  = float4::rep(0.9f);
	//pls.position = Point(1.f, 1.f, 1.f);

	//bath
	pls.intensity  = float4::rep(0.9f);
	pls.position = Point(150.f, 70.f, 100.f);

	integrator.lightSources.push_back(pls);

	integrator.ambientLight = float4::rep(0.1f);

	StratifiedSampler samp;
	samp.addRef();
	samp.samplesX = 2;
	samp.samplesY = 2;


	DefaultSampler defsamp;
	defsamp.addRef();


	starttime = time(NULL);
	std::cout << "render start"<< std::endl;

	//Render
	Renderer r;
	r.integrator = &integrator;
	r.target = &img;

	r.sampler = &defsamp;

	r.camera = &cam1;
	r.render(baum);
	img.writePNG("result.png");

	endtime = time(NULL);
	std::cout << "finish in "<< (endtime-starttime) << std::endl;
	/*int i;
	std::cin >> i;*/

	//For seeing the difference in texture filtering
	/*r.camera = &cam2;
	r.render();
	img.writePNG("result_cam2.png");*/
};


